home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue68 / Alfresco / AARegex.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2001-03-05  |  38.1 KB  |  1,243 lines

  1. {*********************************************************}
  2. {* AARegex                                               *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Regular expression classes       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AARegex;
  14.  
  15. interface
  16.  
  17. {Notes: these classes parse regular expressions that follow this
  18.         grammar:
  19.  
  20.         <anchorexpr> ::= <expr> |
  21.                          '^' <expr> |
  22.                          <expr> '$' |
  23.                          '^' <expr> '$'
  24.         <expr> ::= <term> |
  25.                    <term> '|' <expr>                 - alternation
  26.         <term> ::= <factor> |
  27.                    <factor><term>                    - concatenation
  28.         <factor> ::= <atom> |
  29.                      <atom> '?' |                    - zero or one
  30.                      <atom> '*' |                    - zero or more
  31.                      <atom> '+'                      - one or more
  32.         <atom> ::= <char> |
  33.                    '.' |                             - any char
  34.                    '(' <expr> ') |                   - parentheses
  35.                    '[' <charclass> ']' |             - normal class
  36.                    '[^' <charclass> ']'              - negated class
  37.         <charclass> ::= <charrange> |
  38.                         <charrange><charclass>
  39.         <charrange> ::= <ccchar> |
  40.                         <ccchar> '-' <ccchar>
  41.         <char> ::= <any character except metacharacters> |
  42.                    '\' <any character at all>
  43.         <ccchar> ::= <any character except '-' and ']'> |
  44.                      '\' <any character at all>
  45.  
  46.         This means that parentheses have maximum precedence, followed
  47.         by square brackets, followed by the closure operators,
  48.         followed by concatenation, finally followed by alternation.
  49. }
  50.  
  51. {turn this compiler define on to log the parsing progress and the
  52.  final transition table; file is c:\regexparse.log}
  53. {$DEFINE LogParse}
  54.  
  55. uses
  56.   SysUtils,
  57.   Classes,
  58.   AAIntDeq,
  59.   AAIntLst;
  60.  
  61. type
  62.   TaaRegexParser = class
  63.     private
  64.       FRegexStr : string;
  65.       FPosn     : PAnsiChar;
  66.     protected
  67.       procedure rpParseAtom;
  68.       procedure rpParseCCChar;
  69.       procedure rpParseChar;
  70.       procedure rpParseCharClass;
  71.       procedure rpParseCharRange;
  72.       procedure rpParseExpr;
  73.       procedure rpParseFactor;
  74.       procedure rpParseTerm;
  75.     public
  76.       constructor Create(const aRegexStr : string);
  77.       destructor Destroy; override;
  78.  
  79.       function Parse(var aErrorPos : integer) : boolean;
  80.   end;
  81.  
  82. type
  83.   PaaCharSet = ^TaaCharSet;
  84.   TaaCharSet = set of char;
  85.  
  86.   TaaNFAMatchType = (  {types of matching performed...}
  87.      mtNone,           {..no match (an epsilon no-cost move)}
  88.      mtAnyChar,        {..any character}
  89.      mtChar,           {..a particular character}
  90.      mtClass,          {..a character class}
  91.      mtNegClass,       {..a negated character class}
  92.      mtTerminal,       {..the final state--no matching}
  93.      mtUnused);        {..an unused state--no matching}
  94.  
  95.   TaaRegexError = (    {error codes for invalid regex strings}
  96.      recNone,          {..no error}
  97.      recSuddenEnd,     {..unexpected end of string}
  98.      recMetaChar,      {..read metacharacter, but needed normal char}
  99.      recNoCloseParen,  {..expected close paren, but not there}
  100.      recExtraChars     {..not at end of string after parsing regex}
  101.      );
  102.  
  103.   TaaUpcaseChar = function (aCh : char) : char;
  104.  
  105.   TaaRegexCompiler = class
  106.     private
  107.       FAnchorEnd  : boolean;
  108.       FAnchorStart: boolean;
  109.       FErrorCode  : TaaRegexError;
  110.       FIgnoreCase : boolean;
  111.       FPosn       : PAnsiChar;
  112.       FRegexStr   : string;
  113.       FStartState : integer;
  114.       FTable      : TList;
  115.       FUpcase     : TaaUpcaseChar;
  116.       {$IFDEF LogParse}
  117.       Log : System.Text;
  118.       {$ENDIF}
  119.     protected
  120.       procedure rcSetIgnoreCase(aValue : boolean);
  121.       procedure rcSetRegexStr(const aRegexStr : string);
  122.       procedure rcSetUpcase(aValue : TaaUpcaseChar);
  123.  
  124.       procedure rcClear;
  125.       procedure rcLevel1Optimize;
  126.       procedure rcLevel2Optimize;
  127.       function rcMatchSubString(const S   : string;
  128.                                 StartPosn : integer) : boolean;
  129.       function rcAddState(aMatchType : TaaNFAMatchType;
  130.                           aChar      : char;
  131.                           aCharClass : PaaCharSet;
  132.                           aNextState1: integer;
  133.                           aNextState2: integer) : integer;
  134.       function rcSetState(aState     : integer;
  135.                           aNextState1: integer;
  136.                           aNextState2: integer) : integer;
  137.  
  138.       function rcParseAnchorExpr : integer;
  139.       function rcParseAtom : integer;
  140.       function rcParseCCChar : char;
  141.       function rcParseChar : integer;
  142.       function rcParseCharClass(aClass : PaaCharSet) : boolean;
  143.       function rcParseCharRange(aClass : PaaCharSet) : boolean;
  144.       function rcParseExpr : integer;
  145.       function rcParseFactor : integer;
  146.       function rcParseTerm : integer;
  147.  
  148.       procedure rcWalkNoCostTree(aList  : TaaIntList;
  149.                                  aState : integer);
  150.  
  151.       {$IFDEF LogParse}
  152.       procedure rcDumpTable;
  153.       {$ENDIF}
  154.     public
  155.       constructor Create(const aRegexStr : string);
  156.       destructor Destroy; override;
  157.  
  158.       function Parse(var aErrorPos : integer;
  159.                      var aErrorCode: TaaRegexError) : boolean;
  160.       function MatchString(const S : string) : integer;
  161.  
  162.  
  163.       property IgnoreCase : boolean
  164.                   read FIgnoreCase write rcSetIgnoreCase;
  165.       property RegexString : string
  166.                   read FRegexStr write rcSetRegexStr;
  167.       property Upcase : TaaUpcaseChar
  168.                   read FUpcase write rcSetUpcase;
  169.   end;
  170.  
  171. implementation
  172.  
  173. const
  174.   MetaCharacters : set of char =
  175.                    ['[', ']', '(', ')', '|', '*', '+', '?', '-', '.',
  176.                     '^', '$'];
  177.   {some handy constants}
  178.   UnusedState = -1;
  179.   NewFinalState = -2;
  180.   CreateNewState = -3;
  181.   ErrorState = -4;
  182.   MustScan = -5;
  183.  
  184. type
  185.   PaaNFAState = ^TaaNFAState;
  186.   TaaNFAState = record
  187.     sdNextState1: integer;
  188.     sdNextState2: integer;
  189.     sdNextList  : TaaIntList;
  190.     sdClass     : PaaCharSet;
  191.     sdMatchType : TaaNFAMatchType;
  192.     sdChar      : char;
  193.   end;
  194.  
  195.  
  196. {===TaaRegexParser===================================================}
  197. constructor TaaRegexParser.Create(const aRegexStr : string);
  198. begin
  199.   inherited Create;
  200.   FRegexStr := aRegexStr;
  201. end;
  202. {--------}
  203. destructor TaaRegexParser.Destroy;
  204. begin
  205.   inherited Destroy;
  206. end;
  207. {--------}
  208. function TaaRegexParser.Parse(var aErrorPos : integer) : boolean;
  209. begin
  210.   Result := true;
  211.   aErrorPos := 0;
  212.   FPosn := PAnsiChar(FRegexStr);
  213.   try
  214.     rpParseExpr;
  215.     if (FPosn^ <> #0) then begin
  216.       Result := false;
  217.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  218.     end;
  219.   except
  220.     on E:Exception do begin
  221.       Result := false;
  222.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  223.     end;
  224.   end;
  225. end;
  226. {--------}
  227. procedure TaaRegexParser.rpParseAtom;
  228. begin
  229.   case FPosn^ of
  230.     '(' : begin
  231.             inc(FPosn);
  232.             writeln('open paren');
  233.             rpParseExpr;
  234.             if (FPosn^ <> ')') then
  235.               raise Exception.Create('Regex error: expecting a closing parenthesis');
  236.             inc(FPosn);
  237.             writeln('close paren');
  238.           end;
  239.     '[' : begin
  240.             inc(FPosn);
  241.             if (FPosn^ = '^') then begin
  242.               inc(FPosn);
  243.               writeln('negated char class');
  244.               rpParseCharClass;
  245.             end
  246.             else begin
  247.               writeln('normal char class');
  248.               rpParseCharClass;
  249.             end;
  250.             inc(FPosn);
  251.           end;
  252.     '.' : begin
  253.             inc(FPosn);
  254.             writeln('any character');
  255.           end;
  256.   else
  257.     rpParseChar;
  258.   end;{case}
  259. end;
  260. {--------}
  261. procedure TaaRegexParser.rpParseCCChar;
  262. begin
  263.   if (FPosn^ = #0) then
  264.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  265.   if FPosn^ in [']', '-'] then
  266.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  267.   if (FPosn^ = '\') then begin
  268.     inc(FPosn);
  269.     writeln('escaped ccchar ', FPosn^);
  270.     inc(FPosn);
  271.   end
  272.   else begin
  273.     writeln('ccchar ', FPosn^);
  274.     inc(FPosn);
  275.   end;
  276. end;
  277. {--------}
  278. procedure TaaRegexParser.rpParseChar;
  279. begin
  280.   if (FPosn^ = #0) then
  281.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  282.   if FPosn^ in MetaCharacters then
  283.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  284.   if (FPosn^ = '\') then begin
  285.     inc(FPosn);
  286.     writeln('escaped char ', FPosn^);
  287.     inc(FPosn);
  288.   end
  289.   else begin
  290.     writeln('char ', FPosn^);
  291.     inc(FPosn);
  292.   end;
  293. end;
  294. {--------}
  295. procedure TaaRegexParser.rpParseCharClass;
  296. begin
  297.   rpParseCharRange;
  298.   if (FPosn^ <> ']') then
  299.     rpParseCharClass;
  300. end;
  301. {--------}
  302. procedure TaaRegexParser.rpParseCharRange;
  303. begin
  304.   rpParseCCChar;
  305.   if (FPosn^ = '-') then begin
  306.     inc(FPosn);
  307.     writeln('--range to--');
  308.     rpParseCCChar;
  309.   end;
  310. end;
  311. {--------}
  312. procedure TaaRegexParser.rpParseExpr;
  313. begin
  314.   rpParseTerm;
  315.   if (FPosn^ = '|') then begin
  316.     inc(FPosn);
  317.     writeln('alternation');
  318.     rpParseExpr;
  319.   end;
  320. end;
  321. {--------}
  322. procedure TaaRegexParser.rpParseFactor;
  323. begin
  324.   rpParseAtom;
  325.   case FPosn^ of
  326.     '?' : begin
  327.             inc(FPosn);
  328.             writeln('zero or one');
  329.           end;
  330.     '*' : begin
  331.             inc(FPosn);
  332.             writeln('zero or more');
  333.           end;
  334.     '+' : begin
  335.             inc(FPosn);
  336.             writeln('one or more');
  337.           end;
  338.   end;{case}
  339. end;
  340. {--------}
  341. procedure TaaRegexParser.rpParseTerm;
  342. begin
  343.   rpParseFactor;
  344.   {Note: we have to "break the grammar" here. We've parsed a regular
  345.          subexpression and we're possibly following on with another
  346.          regular subexpression. There's no nice operator to key off
  347.          for concatenation: we just have to know that for
  348.          concatenating two subexpressions, the current character will
  349.          be
  350.            - an open parenthesis
  351.            - an open square bracket
  352.            - an any char operator
  353.            - a character that's not a metacharacter
  354.          i.e., the three possibilities for the start of an "atom" in
  355.          our grammar}
  356.   if (FPosn^ = '(') or
  357.      (FPosn^ = '[') or
  358.      (FPosn^ = '.') or
  359.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then
  360.     rpParseTerm;
  361. end;
  362. {====================================================================}
  363.  
  364.  
  365. {===TaaRegexCompiler===================================================}
  366. constructor TaaRegexCompiler.Create(const aRegexStr : string);
  367. begin
  368.   inherited Create;
  369.   FRegexStr := aRegexStr;
  370.   FIgnoreCase := true;
  371.   FUpcase := System.Upcase;
  372.   FTable := TList.Create;
  373.   FTable.Capacity := 64;
  374. end;
  375. {--------}
  376. destructor TaaRegexCompiler.Destroy;
  377. begin
  378.   if (FTable <> nil) then begin
  379.     rcClear;
  380.     FTable.Free;
  381.   end;
  382.   inherited Destroy;
  383. end;
  384. {--------}
  385. function TaaRegexCompiler.MatchString(const S : string) : integer;
  386. var
  387.   i : integer;
  388.   ErrorPos  : integer;
  389.   ErrorCode : TaaRegexError;
  390. begin
  391.   {if the regex string hasn't been parsed yet, do so}
  392.   if (FTable.Count = 0) then begin
  393.     if not Parse(ErrorPos, ErrorCode) then begin
  394.       raise Exception.Create(
  395.          Format('The regex was invalid at position %d', [ErrorPos]));
  396.     end;
  397.   end;
  398.   {now try and see if the string matches (empty strings don't)}
  399.   Result := 0;
  400.   if (S <> '') then
  401.     {if the regex specified a start anchor, then we only need to check
  402.      the string starting at the first position}
  403.     if FAnchorStart then begin
  404.       if rcMatchSubString(S, 1) then
  405.         Result := 1;
  406.     end
  407.     {otherwise we try and match the string at every position and
  408.      return at the first success}
  409.     else begin
  410.       for i := 1 to length(S) do
  411.         if rcMatchSubString(S, i) then begin
  412.           Result := i;
  413.           Break;
  414.         end;
  415.     end;
  416. end;
  417. {--------}
  418. function TaaRegexCompiler.Parse(var aErrorPos : integer;
  419.                                 var aErrorCode: TaaRegexError)
  420.                                                             : boolean;
  421.   {$IFDEF LogParse}
  422.   procedure WriteError(aErrorPos : integer;
  423.                        aErrorCode: TaaRegexError);
  424.   begin
  425.     writeln(Log, '***parse error found at ', aErrorPos);
  426.     case aErrorCode of
  427.       recNone         : writeln(Log, '-->no error');
  428.       recSuddenEnd    : writeln(Log, '-->unexpected end of regex');
  429.       recMetaChar     : writeln(Log, '-->found metacharacter in wrong place');
  430.       recNoCloseParen : writeln(Log, '-->missing close paren');
  431.       recExtraChars   : writeln(Log, '-->extra chars after valid regex');
  432.     end;
  433.     writeln(Log, '"', FRegexStr, '"');
  434.     writeln(Log, '^':succ(aErrorPos));
  435.   end;
  436.   {$ENDIF}
  437. begin
  438.   {$IFDEF LogParse}
  439.   System.Assign(Log, 'c:\regexparse.log');
  440.   System.Rewrite(Log);
  441.   try
  442.     writeln(Log, 'Parsing regex: "', FRegexStr, '"');
  443.   {$ENDIF}
  444.  
  445.   {clear the current transition table}
  446.   rcClear;
  447.   {empty regex strings are not allowed}
  448.   if (FRegexStr = '') then begin
  449.     Result := false;
  450.     aErrorPos := 1;
  451.     aErrorCode := recSuddenEnd;
  452.  
  453.     {$IFDEF LogParse}
  454.     WriteError(aErrorPos, aErrorCode);
  455.     {$ENDIF}
  456.  
  457.     Exit;
  458.   end;
  459.   {parse the regex string}
  460.   FPosn := PAnsiChar(FRegexStr);
  461.   FStartState := rcParseAnchorExpr;
  462.   {if an error occurred or we're not at the end of the regex string,
  463.    clear the transition table, return false and the error position}
  464.   if (FStartState = ErrorState) or (FPosn^ <> #0) then begin
  465.     if (FStartState <> ErrorState) and (FPosn^ <> #0) then
  466.       FErrorCode := recExtraChars;
  467.     rcClear;
  468.     Result := false;
  469.     aErrorPos := succ(FPosn - PAnsiChar(FRegexStr));
  470.     aErrorCode := FErrorCode;
  471.  
  472.     {$IFDEF LogParse}
  473.     WriteError(aErrorPos, aErrorCode);
  474.     {$ENDIF}
  475.   end
  476.   {otherwise add a terminal state, optimize, return true}
  477.   else begin
  478.     rcAddState(mtTerminal, #0, nil, UnusedState, UnusedState);
  479.     rcLevel1Optimize;
  480.     rcLevel2Optimize;
  481.     Result := true;
  482.     aErrorPos := 0;
  483.     aErrorCode := recNone;
  484.  
  485.     {$IFDEF LogParse}
  486.     rcDumpTable;
  487.     {$ENDIF}
  488.   end;
  489.  
  490.   {$IFDEF LogParse}
  491.   finally
  492.     System.Close(Log);
  493.   end;
  494.   {$ENDIF}
  495. end;
  496. {--------}
  497. function TaaRegexCompiler.rcAddState(aMatchType : TaaNFAMatchType;
  498.                                      aChar      : char;
  499.                                      aCharClass : PaaCharSet;
  500.                                      aNextState1: integer;
  501.                                      aNextState2: integer) : integer;
  502. var
  503.   StateData : PaaNFAState;
  504. begin
  505.   {create the new state record}
  506.   StateData := AllocMem(sizeof(TaaNFAState));
  507.   {set up the fields in the state record}
  508.   if (aNextState1 = NewFinalState) then
  509.     StateData^.sdNextState1 := succ(FTable.Count)
  510.   else
  511.     StateData^.sdNextState1 := aNextState1;
  512.   StateData^.sdNextState2 := aNextState2;
  513.   StateData^.sdMatchType := aMatchType;
  514.   if (aMatchType = mtChar) then
  515.     StateData^.sdChar := aChar
  516.   else if (aMatchType = mtClass) or (aMatchType = mtNegClass) then
  517.     StateData^.sdClass := aCharClass;
  518.   {add the new state}
  519.   Result := FTable.Count;
  520.   FTable.Add(StateData);
  521. end;
  522. {--------}
  523. procedure TaaRegexCompiler.rcClear;
  524. var
  525.   i : integer;
  526.   StateData : PaaNFAState;
  527. begin
  528.   {free all items in the state transition table}
  529.   for i := 0 to pred(FTable.Count) do begin
  530.     StateData := PaaNFAState(FTable.List^[i]);
  531.     if (StateData <> nil) then begin
  532.       with StateData^ do begin
  533.         if (sdMatchType = mtClass) or
  534.            (sdMatchType = mtNegClass) then
  535.           if (sdClass <> nil) then
  536.             FreeMem(StateData^.sdClass);
  537.         sdNextList.Free; 
  538.       end;
  539.       Dispose(StateData);
  540.     end;
  541.   end;
  542.   {clear the state transition table}
  543.   FTable.Clear;
  544.   FTable.Capacity := 64;
  545.   FAnchorStart := false;
  546.   FAnchorEnd := false;
  547. end;
  548. {--------}
  549. {$IFDEF LogParse}
  550. procedure TaaRegexCompiler.rcDumpTable;
  551. var
  552.   i, j : integer;
  553. begin
  554.   writeln(Log);
  555.   if (FTable.Count = 0) then
  556.     writeln(Log, 'No transition table to dump!')
  557.   else begin
  558.     writeln(Log, 'Transition table dump for "', FRegexStr, '"');
  559.     if FAnchorStart then
  560.       writeln(Log, 'anchored at start of string');
  561.     if FAnchorEnd then
  562.       writeln(Log, 'anchored at end of string');
  563.     writeln(Log, 'start state: ', FStartState:3);
  564.     for i := 0 to pred(FTable.Count) do begin
  565.       write(Log, i:3);
  566.       with PaaNFAState(FTable[i])^ do begin
  567.         case sdMatchType of
  568.           mtNone    : write(Log, '  no match');
  569.           mtAnyChar : write(Log, '  any char');
  570.           mtChar    : write(Log, '    char:', sdChar);
  571.           mtClass   : write(Log, '     class');
  572.           mtNegClass: write(Log, ' neg class');
  573.           mtTerminal: write(Log, '*******END');
  574.           mtUnused  : write(Log, '        --');
  575.         else
  576.           write(Log, ' **error**');
  577.         end;
  578.         if (sdNextList <> nil) then begin
  579.           write(Log, ' next:');
  580.           for j := 0 to pred(sdNextList.Count) do
  581.             write(Log, ' ', sdNextList[j]);
  582.         end;
  583.       end;
  584.       writeln(Log);
  585.     end;
  586.   end;
  587. end;
  588. {$ENDIF}
  589. {--------}
  590. procedure TaaRegexCompiler.rcLevel1Optimize;
  591. var
  592.   i : integer;
  593.   Walker : PaaNFAState;
  594. begin
  595.   {level 1 optimization removes all states that have only a single
  596.    no-cost move to another state}
  597.  
  598.   {cycle through all the state records, except for the last one}
  599.   for i := 0 to (FTable.Count - 2) do begin
  600.     {get this state}
  601.     with PaaNFAState(FTable.List^[i])^ do begin
  602.       {walk the chain pointed to by the first next state, unlinking
  603.        the states that are simple single no-cost moves}
  604.       Walker := PaaNFAState(FTable.List^[sdNextState1]);
  605.       while (Walker^.sdMatchType = mtNone) and
  606.             (Walker^.sdNextState2 = UnusedState) do begin
  607.         sdNextState1 := Walker^.sdNextState1;
  608.         Walker := PaaNFAState(FTable.List^[sdNextState1]);
  609.       end;
  610.       {walk the chain pointed to by the first next state, unlinking
  611.        the states that are simple single no-cost moves}
  612.       if (sdNextState2 <> UnusedState) then begin
  613.         Walker := PaaNFAState(FTable.List^[sdNextState2]);
  614.         while (Walker^.sdMatchType = mtNone) and
  615.               (Walker^.sdNextState2 = UnusedState) do begin
  616.           sdNextState2 := Walker^.sdNextState1;
  617.           Walker := PaaNFAState(FTable.List^[sdNextState2]);
  618.         end;
  619.       end;
  620.     end;
  621.   end;
  622. end;
  623. {--------}
  624. procedure TaaRegexCompiler.rcLevel2Optimize;
  625. var
  626.   i : integer;
  627. begin
  628.   {level 2 optimization removes all no-cost moves}
  629.  
  630.   {cycle through all the state records, except for the last one}
  631.   for i := 0 to (FTable.Count - 2) do begin
  632.     {get this state}
  633.     with PaaNFAState(FTable.List^[i])^ do begin
  634.       {if it's not a no-cost move state...}
  635.       if (sdMatchType <> mtNone) then begin
  636.         {create the state list}
  637.         sdNextList := TaaIntList.Create;
  638.         {walk the chain pointed to by the first next state, adding
  639.          the non-no-cost states to the list}
  640.         rcWalkNoCostTree(sdNextList, sdNextState1);
  641.       end;
  642.     end;
  643.   end;
  644.  
  645.   {cycle through all the state records, except for the last one,
  646.    marking unused ones--not strictly necessary but good for debugging}
  647.   for i := 0 to (FTable.Count - 2) do begin
  648.     with PaaNFAState(FTable.List^[i])^ do begin
  649.       if (sdMatchType = mtNone) then
  650.         sdMatchType := mtUnused;
  651.     end;
  652.   end;
  653. end;
  654. {--------}
  655. function TaaRegexCompiler.rcMatchSubString(const S   : string;
  656.                                            StartPosn : integer)
  657.                                                             : boolean;
  658. var
  659.   i      : integer;                                                       
  660.   Ch     : char;
  661.   State  : integer;
  662.   Deque  : TaaIntDeque;
  663.   StrInx : integer;
  664. begin
  665.   {assume we fail to match}
  666.   Result := false;
  667.   {create the deque}
  668.   Deque := TaaIntDeque.Create(64);
  669.   try
  670.     {enqueue the special value to start scanning}
  671.     Deque.Enqueue(MustScan);
  672.     {enqueue the first state}
  673.     Deque.Enqueue(FStartState);
  674.     {prepare the string index}
  675.     StrInx := StartPosn - 1;
  676.     Ch := #0; //just to fool the compiler
  677.     {loop until the deque is empty or we run out of string}
  678.     while (StrInx <= length(S)) and not Deque.IsEmpty do begin
  679.       {pop the top state from the deque}
  680.       State := Deque.Pop;
  681.       {process the "must scan" state first}
  682.       if (State = MustScan) then begin
  683.         {if the deque is empty at this point, we might as well give up
  684.          since there are no states left to process new characters}
  685.         if not Deque.IsEmpty then begin
  686.           {if we haven't run out of string, get the character, and
  687.            enqueue the "must scan" state again}
  688.           inc(StrInx);
  689.           if (StrInx <= length(S)) then begin
  690.             if IgnoreCase then
  691.               Ch := Upcase(S[StrInx])
  692.             else
  693.               Ch := S[StrInx];
  694.             Deque.Enqueue(MustScan);
  695.           end;
  696.         end;
  697.       end
  698.       {otherwise, process the state}
  699.       else with PaaNFAState(FTable.List^[State])^ do begin
  700.         case sdMatchType of
  701.           mtNone :
  702.             begin
  703.               Assert(false, 'no-cost states shouldn''t be seen');
  704.             end;
  705.           mtAnyChar :
  706.             begin
  707.               {for a match of any character, enqueue the next states}
  708.               for i := 0 to pred(sdNextList.Count) do
  709.                 Deque.Enqueue(sdNextList[i]);
  710.             end;
  711.           mtChar :
  712.             begin
  713.               {for a match of a character, enqueue the next states}
  714.               if (Ch = sdChar) then
  715.                 for i := 0 to pred(sdNextList.Count) do
  716.                   Deque.Enqueue(sdNextList[i]);
  717.             end;
  718.           mtClass :
  719.             begin
  720.               {for a match within a class, enqueue the next states}
  721.               if (Ch in sdClass^) then
  722.                 for i := 0 to pred(sdNextList.Count) do
  723.                   Deque.Enqueue(sdNextList[i]);
  724.             end;
  725.           mtNegClass :
  726.             begin
  727.               {for a match not within a class, enqueue the next states}
  728.               if not (Ch in sdClass^) then
  729.                 for i := 0 to pred(sdNextList.Count) do
  730.                   Deque.Enqueue(sdNextList[i]);
  731.             end;
  732.           mtTerminal :
  733.             begin
  734.               {for a terminal state, the string successfully matched
  735.                if the regex had no end anchor, or we're at the end
  736.                of the string}
  737.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  738.                 Result := true;
  739.                 Exit;
  740.               end;
  741.             end;
  742.           mtUnused :
  743.             begin
  744.               Assert(false, 'unused states shouldn''t be seen');
  745.             end;
  746.         end;
  747.       end;
  748.     end;
  749.     {if we reach this point we've either exhausted the deque or we've
  750.      run out of string; if the former, the substring did not match
  751.      since there are no more states. If the latter, we need to check
  752.      the states left on the deque to see if one is the terminating
  753.      state; if so the string matched the regular expression defined by
  754.      the transition table}
  755.     while not Deque.IsEmpty do begin
  756.       State := Deque.Pop;
  757.       with PaaNFAState(FTable.List^[State])^ do begin
  758.         case sdMatchType of
  759.           mtTerminal :
  760.             begin
  761.               {for a terminal state, the string successfully matched
  762.                if the regex had no end anchor, or we're at the end
  763.                of the string}
  764.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  765.                 Result := true;
  766.                 Exit;
  767.               end;
  768.             end;
  769.         end;{case}
  770.       end;
  771.     end;
  772.   finally
  773.     Deque.Free;
  774.   end;
  775. end;
  776. {--------}
  777. function TaaRegexCompiler.rcParseAnchorExpr : integer;
  778. begin
  779.   {check for an initial '^'}
  780.   if (FPosn^ = '^') then begin
  781.     FAnchorStart := true;
  782.     inc(FPosn);
  783.  
  784.     {$IFDEF LogParse}
  785.     writeln(Log, 'parsed start anchor');
  786.     {$ENDIF}
  787.   end;
  788.  
  789.   {parse an expression}
  790.   Result := rcParseExpr;
  791.  
  792.   {if we were successful, check for the final '$'}
  793.   if (Result <> ErrorState) then begin
  794.     if (FPosn^ = '$') then begin
  795.       FAnchorEnd := true;
  796.       inc(FPosn);
  797.  
  798.       {$IFDEF LogParse}
  799.       writeln(Log, 'parsed end anchor');
  800.       {$ENDIF}
  801.     end;
  802.   end;
  803. end;
  804. {--------}
  805. function TaaRegexCompiler.rcParseAtom : integer;
  806. var
  807.   MatchType : TaaNFAMatchType;
  808.   CharClass : PaaCharSet;
  809. begin
  810.   case FPosn^ of
  811.     '(' :
  812.       begin
  813.         {move past the open parenthesis}
  814.         inc(FPosn);
  815.  
  816.         {$IFDEF LogParse}
  817.         writeln(Log, 'parsed open paren');
  818.         {$ENDIF}
  819.  
  820.         {parse a complete regex between the parentheses}
  821.         Result := rcParseExpr;
  822.         if (Result = ErrorState) then
  823.           Exit;
  824.         {if the current character is not a close parenthesis,
  825.          there's an error}
  826.         if (FPosn^ <> ')') then begin
  827.           FErrorCode := recNoCloseParen;
  828.           Result := ErrorState;
  829.           Exit;
  830.         end;
  831.         {move past the close parenthesis}
  832.         inc(FPosn);
  833.  
  834.         {$IFDEF LogParse}
  835.         writeln(Log, 'parsed close paren');
  836.         {$ENDIF}
  837.       end;
  838.     '[' :
  839.       begin
  840.         {move past the open square bracket}
  841.         inc(FPosn);
  842.  
  843.         {$IFDEF LogParse}
  844.         writeln(Log, 'parsed open square bracket (start of class)');
  845.         {$ENDIF}
  846.  
  847.         {if the first character in the class is a '^' then the
  848.          class if negated, otherwise it's a normal one}
  849.         if (FPosn^ = '^') then begin
  850.           inc(FPosn);
  851.           MatchType := mtNegClass;
  852.  
  853.           {$IFDEF LogParse}
  854.           writeln(Log, 'it is a negated class');
  855.           {$ENDIF}
  856.         end
  857.         else begin
  858.           MatchType := mtClass;
  859.  
  860.           {$IFDEF LogParse}
  861.           writeln(Log, 'it is a normal class');
  862.           {$ENDIF}
  863.         end;
  864.         {allocate the class character set and parse the character
  865.          class; this will return either with an error, or when the
  866.          closing square bracket is encountered}
  867.         New(CharClass);
  868.         CharClass^ := [];
  869.         if not rcParseCharClass(CharClass) then begin
  870.           Dispose(CharClass);
  871.           Result := ErrorState;
  872.           Exit;
  873.         end;
  874.         {move past the closing square bracket}
  875.         Assert(FPosn^ = ']',
  876.                'the rcParseCharClass terminated without finding a "]"');
  877.         inc(FPosn);
  878.  
  879.         {$IFDEF LogParse}
  880.         writeln(Log, 'parsed close square bracket (end of class)');
  881.         {$ENDIF}
  882.  
  883.         {add a new state for the character class}
  884.         Result := rcAddState(MatchType, #0, CharClass,
  885.                              NewFinalState, UnusedState);
  886.       end;
  887.     '.' :
  888.       begin
  889.         {move past the period metacharacter}
  890.         inc(FPosn);
  891.  
  892.         {$IFDEF LogParse}
  893.         writeln(Log, 'parsed anychar operator "."');
  894.         {$ENDIF}
  895.  
  896.         {add a new state for the 'any character' token}
  897.         Result := rcAddState(mtAnyChar, #0, nil,
  898.                              NewFinalState, UnusedState);
  899.       end;
  900.   else
  901.     {otherwise parse a single character}
  902.     Result := rcParseChar;
  903.   end;{case}
  904. end;
  905. {--------}
  906. function TaaRegexCompiler.rcParseCCChar : char;
  907. begin
  908.   {if we hit the end of the string, it's an error}
  909.   if (FPosn^ = #0) then begin
  910.     FErrorCode := recSuddenEnd;
  911.     Result := #0;
  912.     Exit;
  913.   end;
  914.   {if the current char is a metacharacter (at least in terms of a
  915.    character class), it's an error}
  916.   if FPosn^ in [']', '-'] then begin
  917.     FErrorCode := recMetaChar;
  918.     Result := #0;
  919.     Exit;
  920.   end;
  921.   {otherwise return the character and advance past it}
  922.   if (FPosn^ = '\') then
  923.     {..it's an escaped character: get the next character instead}
  924.     inc(FPosn);
  925.   Result := FPosn^;
  926.   inc(FPosn);
  927.  
  928.   {$IFDEF LogParse}
  929.   writeln(Log, 'parsed charclass char: "', Result, '"');
  930.   {$ENDIF}
  931. end;
  932. {--------}
  933. function TaaRegexCompiler.rcParseChar : integer;
  934. var
  935.   Ch : char;
  936. begin
  937.   {if we hit the end of the string, it's an error}
  938.   if (FPosn^ = #0) then begin
  939.     Result := ErrorState;
  940.     FErrorCode := recSuddenEnd;
  941.     Exit;
  942.   end;
  943.   {if the current char is one of the metacharacters, it's an error}
  944.   if FPosn^ in MetaCharacters then begin
  945.     Result := ErrorState;
  946.     FErrorCode := recMetaChar;
  947.     Exit;
  948.   end;
  949.   {otherwise add a state for the character}
  950.   {..if it's an escaped character: get the next character instead}
  951.   if (FPosn^ = '\') then
  952.     inc(FPosn);
  953.   if IgnoreCase then
  954.     Ch := Upcase(FPosn^)
  955.   else
  956.     Ch := FPosn^;
  957.   Result := rcAddState(mtChar, Ch, nil, NewFinalState, UnusedState);
  958.   inc(FPosn);
  959.  
  960.   {$IFDEF LogParse}
  961.   writeln(Log, 'parsed char: "', Ch, '"');
  962.   {$ENDIF}
  963. end;
  964. {--------}
  965. function TaaRegexCompiler.rcParseCharClass(aClass : PaaCharSet) : boolean;
  966. begin
  967.   {assume we can't parse a character class properly}
  968.   Result := false;
  969.   {parse a character range; if we can't there was an error and the
  970.    caller will take care of it}
  971.   if not rcParseCharRange(aClass) then
  972.     Exit;
  973.   {if the current character was not the right bracket, parse another
  974.    character class (note: we're removing the tail recursion here)}
  975.   while (FPosn^ <> ']') do begin
  976.     if not rcParseCharRange(aClass) then
  977.       Exit;
  978.   end;
  979.   {if we reach here we were successful}
  980.   Result := true;
  981. end;
  982. {--------}
  983. function TaaRegexCompiler.rcParseCharRange(aClass : PaaCharSet) : boolean;
  984. var
  985.   StartChar : char;
  986.   EndChar   : char;
  987.   Ch        : char;
  988. begin
  989.   {assume we can't parse a character range properly}
  990.   Result := false;
  991.   {parse a single character; if it's null there was an error}
  992.   StartChar := rcParseCCChar;
  993.   if (StartChar = #0) then
  994.     Exit;
  995.   {if the current character is not a dash, the range consisted of a
  996.    single character}
  997.   if (FPosn^ <> '-') then begin
  998.     if IgnoreCase then
  999.       Include(aClass^, Upcase(StartChar))
  1000.     else
  1001.       Include(aClass^, StartChar)
  1002.   end
  1003.   {otherwise it's a real range, so get the character at the end of the
  1004.    range; if that's null, there was an error}
  1005.   else begin
  1006.  
  1007.     {$IFDEF LogParse}
  1008.     writeln(Log, '-range to-');
  1009.     {$ENDIF}
  1010.  
  1011.     inc(FPosn); {move past the '-'}
  1012.     EndChar := rcParseCCChar;
  1013.     if (EndChar = #0) then
  1014.       Exit;
  1015.     {build the range as a character set}
  1016.     if (StartChar > EndChar) then begin
  1017.       Ch := StartChar;
  1018.       StartChar := EndChar;
  1019.       EndChar := Ch;
  1020.     end;
  1021.     for Ch := StartChar to EndChar do begin
  1022.       Include(aClass^, Ch);
  1023.       if IgnoreCase then
  1024.         Include(aClass^, Upcase(Ch));
  1025.     end;
  1026.   end;
  1027.   {if we reach here we were successful}
  1028.   Result := true;
  1029. end;
  1030. {--------}
  1031. function TaaRegexCompiler.rcParseExpr : integer;
  1032. var
  1033.   StartState1 : integer;
  1034.   StartState2 : integer;
  1035.   EndState1   : integer;
  1036.   OverallStartState : integer;
  1037. begin
  1038.   {assume the worst}
  1039.   Result := ErrorState;
  1040.   {parse an initial term}
  1041.   StartState1 := rcParseTerm;
  1042.   if (StartState1 = ErrorState) then
  1043.     Exit;
  1044.   {if the current character is *not* a pipe character, no alternation
  1045.    is present so return the start state of the initial term as our
  1046.    start state}
  1047.   if (FPosn^ <> '|') then
  1048.     Result := StartState1
  1049.   {otherwise, we need to parse another expr and join the two together
  1050.    in the transition table}
  1051.   else begin
  1052.  
  1053.     {$IFDEF LogParse}
  1054.     writeln(Log, 'OR (alternation)');
  1055.     {$ENDIF}
  1056.  
  1057.     {advance past the pipe}
  1058.     inc(FPosn);
  1059.     {the initial term's end state does not exist yet (although there
  1060.      is a state in the term that points to it), so create it}
  1061.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1062.     {for the OR construction we need a new initial state: it will
  1063.      point to the initial term and the second just-about-to-be-parsed
  1064.      expr}
  1065.     OverallStartState := rcAddState(mtNone, #0, nil,
  1066.                                     UnusedState, UnusedState);
  1067.     {parse another expr}
  1068.     StartState2 := rcParseExpr;
  1069.     if (StartState2 = ErrorState) then
  1070.       Exit;
  1071.     {alter the state state for the overall expr so that the second
  1072.      link points to the start of the second expr}
  1073.     Result := rcSetState(OverallStartState, StartState1, StartState2);
  1074.     {now set the end state for the initial term to point to the final
  1075.      end state for the second expr and the overall expr}
  1076.     rcSetState(EndState1, FTable.Count, UnusedState);
  1077.   end;
  1078. end;
  1079. {--------}
  1080. function TaaRegexCompiler.rcParseFactor : integer;
  1081. var
  1082.   StartStateAtom : integer;
  1083.   EndStateAtom   : integer;
  1084. begin
  1085.   {assume the worst}
  1086.   Result := ErrorState;
  1087.   {first parse an atom}
  1088.   StartStateAtom := rcParseAtom;
  1089.   if (StartStateAtom = ErrorState) then
  1090.     Exit;
  1091.   {check for a closure operator}
  1092.   case FPosn^ of
  1093.     '?' : begin
  1094.             {$IFDEF LogParse}
  1095.             writeln(Log, 'zero or one closure');
  1096.             {$ENDIF}
  1097.  
  1098.             {move past the ? operator}
  1099.             inc(FPosn);
  1100.             {the atom's end state doesn't exist yet, so create one}
  1101.             EndStateAtom := rcAddState(mtNone, #0, nil,
  1102.                                        UnusedState, UnusedState);
  1103.             {create a new start state for the overall regex}
  1104.             Result := rcAddState(mtNone, #0, nil,
  1105.                                  StartStateAtom, EndStateAtom);
  1106.             {make sure the new end state points to the next unused
  1107.              state}
  1108.             rcSetState(EndStateAtom, FTable.Count, UnusedState);
  1109.           end;
  1110.     '*' : begin
  1111.             {$IFDEF LogParse}
  1112.             writeln(Log, 'zero or more closure');
  1113.             {$ENDIF}
  1114.  
  1115.             {move past the * operator}
  1116.             inc(FPosn);
  1117.             {the atom's end state doesn't exist yet, so create one;
  1118.              it'll be the start of the overall regex subexpression}
  1119.             Result := rcAddState(mtNone, #0, nil,
  1120.                                  NewFinalState, StartStateAtom);
  1121.           end;
  1122.     '+' : begin
  1123.             {$IFDEF LogParse}
  1124.             writeln(Log, 'one or more closure');
  1125.             {$ENDIF}
  1126.  
  1127.             {move past the + operator}
  1128.             inc(FPosn);
  1129.             {the atom's end state doesn't exist yet, so create one}
  1130.             rcAddState(mtNone, #0, nil, NewFinalState, StartStateAtom);
  1131.             {the start of the overall regex subexpression will be the
  1132.              atom's start state}
  1133.             Result := StartStateAtom;
  1134.           end;
  1135.   else
  1136.     Result := StartStateAtom;
  1137.   end;{case}
  1138. end;
  1139. {--------}
  1140. function TaaRegexCompiler.rcParseTerm : integer;
  1141. var
  1142.   StartState2 : integer;
  1143.   EndState1   : integer;
  1144. begin
  1145.   {parse an initial factor, the state number returned will also be our
  1146.    return state number}
  1147.   Result := rcParseFactor;
  1148.   if (Result = ErrorState) then
  1149.     Exit;
  1150.   {Note: we have to "break the grammar" here. We've parsed a regular
  1151.          subexpression and we're possibly following on with another
  1152.          regular subexpression. There's no nice operator to key off
  1153.          for concatenation: we just have to know that for
  1154.          concatenating two subexpressions, the current character will
  1155.          be
  1156.            - an open parenthesis
  1157.            - an open square bracket
  1158.            - an any char operator
  1159.            - a character that's not a metacharacter
  1160.          i.e., the three possibilities for the start of an "atom" in
  1161.          our grammar}
  1162.   if (FPosn^ = '(') or
  1163.      (FPosn^ = '[') or
  1164.      (FPosn^ = '.') or
  1165.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then begin
  1166.     {$IFDEF LogParse}
  1167.     writeln(Log, 'concatenation');
  1168.     {$ENDIF}
  1169.  
  1170.     {the initial factor's end state does not exist yet (although there
  1171.      is a state in the term that points to it), so create it}
  1172.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1173.     {parse another term}
  1174.     StartState2 := rcParseTerm;
  1175.     if (StartState2 = ErrorState) then begin
  1176.       Result := ErrorState;
  1177.       Exit;
  1178.     end;
  1179.     {join the first factor to the second term}
  1180.     rcSetState(EndState1, StartState2, UnusedState);
  1181.   end;
  1182. end;
  1183. {--------}
  1184. procedure TaaRegexCompiler.rcSetIgnoreCase(aValue : boolean);
  1185. begin
  1186.   if (aValue <> FIgnoreCase) then begin
  1187.     rcClear;
  1188.     FIgnoreCase := aValue;
  1189.   end;
  1190. end;
  1191. {--------}
  1192. procedure TaaRegexCompiler.rcSetRegexStr(const aRegexStr : string);
  1193. begin
  1194.   if (aRegexStr <> FRegexStr) then begin
  1195.     rcClear;
  1196.     FRegexStr := aRegexStr;
  1197.   end;
  1198. end;
  1199. {--------}
  1200. function TaaRegexCompiler.rcSetState(aState     : integer;
  1201.                                      aNextState1: integer;
  1202.                                      aNextState2: integer) : integer;
  1203. var
  1204.   StateData : PaaNFAState;
  1205. begin
  1206.   Assert((0 <= aState) and (aState < FTable.Count),
  1207.          'trying to change an invalid state');
  1208.  
  1209.   {get the state record and change the transition information}
  1210.   StateData := PaaNFAState(FTable.List^[aState]);
  1211.   StateData^.sdNextState1 := aNextState1;
  1212.   StateData^.sdNextState2 := aNextState2;
  1213.   Result := aState;
  1214. end;
  1215. {--------}
  1216. procedure TaaRegexCompiler.rcSetUpcase(aValue : TaaUpcaseChar);
  1217. begin
  1218.   if not Assigned(aValue) then
  1219.     FUpcase := System.Upcase
  1220.   else
  1221.     FUpcase := aValue;
  1222. end;
  1223. {--------}
  1224. procedure TaaRegexCompiler.rcWalkNoCostTree(aList  : TaaIntList;
  1225.                                             aState : integer);
  1226. begin
  1227.   {look at this state's record...}
  1228.   with PaaNFAState(FTable.List^[aState])^ do begin
  1229.     {if it's a no-cost state, recursively walk the
  1230.      first, then the second chain}
  1231.     if (sdMatchType = mtNone) then begin
  1232.       rcWalkNoCostTree(aList, sdNextState1);
  1233.       rcWalkNoCostTree(aList, sdNextState2);
  1234.     end
  1235.     {otherwise, add it to the list}
  1236.     else 
  1237.       aList.Add(aState);
  1238.   end;
  1239. end;
  1240. {====================================================================}
  1241.  
  1242. end.
  1243.